package smri;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.io.StreamTokenizer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Eduardo
 */
public class Index implements Serializable{

    private Hashtable<String, ArrayList<IndexItem>> index;
    private Hashtable<String, String> stopWord;
    private ArrayList<Document> documents;
    private Stemmer stemmer;

    //Nombres de los ficheros para salvar y recuperar el indice
    private String fileIndexName = "index.obj";
    private String fileDocumentsName = "documents.obj";

    

    public Index()
    {
        this.index = new Hashtable<String, ArrayList<IndexItem>>();
        this.documents = new ArrayList<Document>();
        loadStopWord();
        this.stemmer = new Stemmer();
    }

    /**
     * Cargamos las stopWord en una hash para luego utilizarlas en el tokenizador
     */
    private void loadStopWord()
    {
        try {
            stopWord = new Hashtable<String, String>();
            StreamTokenizer sTokenizer = new StreamTokenizer(new FileReader("Data\\stopWord.txt"));
            while (sTokenizer.nextToken() != StreamTokenizer.TT_EOF)
                stopWord.put(sTokenizer.sval, sTokenizer.sval);
        }
        catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    /**
     * Aniade las palabras importantes de un documento al indice del buscador     
     */
    public void addDocumentToIndex(Document doc)
    {
        String sText, stemmerText;
        int position = 0;
        StringTokenizer sTokenizer = null;

        //Aniadimos el documento a la lista de documentos
        this.documents.add(doc);
        
               
        //Tokenizamos el titulo y el texto del documento juntos
        sTokenizer = new StringTokenizer(doc.getTittle() + doc.getText(), " ");
        while(sTokenizer.hasMoreTokens())
        {
            sText = sTokenizer.nextToken();

            //PASO 1 -> Eliminamos las stopWords del Titulo
            if(!isStopWords(sText))
            {
                //PASO 2 -> Aplicamos la reduccion a la raiz o stemmer
                stemmerText = stemmer.getStemmer(sText);
                
                if(!index.containsKey(stemmerText))
                {                    
                    ArrayList<IndexItem> a = new ArrayList<IndexItem>();                    
                    a.add(new IndexItem(doc.getId(), position));
                    index.put(stemmerText, a);
                }
                else
                {
                    ArrayList<IndexItem> list = index.get(stemmerText);
                    boolean docExistsInArray = false;
                    for(IndexItem item : list) {
                        if(item.getDocReference() == doc.getId()) {
                            item.getCharPosition().add(position);
                            docExistsInArray = true;
                        }
                    }
                    //Si el documento no tiene referencia dentro del array, hay que crear un item nuevo
                    if(!docExistsInArray)   
                        index.get(stemmerText).add(new IndexItem(doc.getId(), position));                    
                }

                position += sText.length() + 1;
            }
            else
                position += sText.length() + 1;
        }
    }
    

    /**
     * Implementacion del metodo buscar
     * Recibe string de la cadena de busqueda y retorna la lista de similutud de los
     * documentos donde aparece la query
     */
    public ArrayList<IndexItem> search(String searchWord)
    {                
        ArrayList<IndexItem> resultList = new ArrayList<IndexItem>();
        ArrayList<String> cleanQuery = new ArrayList<String>();
        ArrayList<IndexItem> matchDocuments = new ArrayList<IndexItem>(); //Lista de documentos coincidentes
        Hashtable similitudeList = new Hashtable();                              //Almacena las simulitudes de cada doc con la query

        StringTokenizer sTokenizer = new StringTokenizer(searchWord, " ");
        while(sTokenizer.hasMoreElements())
        {
            String word = sTokenizer.nextToken();
            if(!stopWord.contains(word))            
                cleanQuery.add(stemmer.getStemmer(word));            
        }

        //Calculamos la funcion de simulitud de la query y los documentos

        //Realizamos la union de todos los documentos en los aparecen las palabras de la query
        for(String w : cleanQuery)
        {
            ArrayList<IndexItem> list = index.get(w);
            for(IndexItem i : list) {
                if(!documentInList(matchDocuments, i.getDocReference()))
                   matchDocuments.add(i);
            }
        }

        //Calculamos la similitud
        for(IndexItem item : matchDocuments)
        {
            double sim = 0;            
            for(String w : cleanQuery)
                sim += calculateIDF(w) * calculateWeight(w, item.getDocReference());
            similitudeList.put(sim / normalize(cleanQuery, item.getDocReference()), item);
        }

        //Ordenamos de mayor a menor simulitud para ofrecer el ranking de documentos
        Vector v = new Vector(similitudeList.keySet());
        Collections.sort(v);
        Collections.reverse(v);
        Iterator it = v.iterator();
        while(it.hasNext())            
            resultList.add((IndexItem)similitudeList.get((Double)it.next()));

        
        return resultList;
    }


    /**
     * Calcula el denominador de la funcion de similitud (Productos escalares |c| y |d|)
     */
    private double normalize(ArrayList<String> query, int idDoc)
    {
        double c = 0, d = 0;

        //Calculamos la |c|
        for(String q : query)
            c += Math.pow(calculateIDF(q), 2);
        c = Math.sqrt(c);

        //Calculamos la |d|
        Enumeration<String> wordIndexedList = index.keys();
        while(wordIndexedList.hasMoreElements())
        {
            String wordIndexed = wordIndexedList.nextElement();
            d += Math.pow(calculateWeight(wordIndexed, idDoc), 2);
        }
        d = Math.sqrt(d);

        return c * d;
    }

    /**
     * Calcula el peso de un termino en el documento
     */
    private double calculateWeight(String word, int idDocument)
    {
        ArrayList<IndexItem> list = index.get(word);
        double tf = 0;
        for (IndexItem item : list) {
            if(item.getDocReference() == idDocument) {
                tf = item.getCharPosition().size();
                break;
            }
        }
        return tf * calculateIDF(word);
    }

    /**
     * Calculo del idf de una palabra
     */
    private double calculateIDF(String word)
    {
        return Math.log10((double)documents.size() / (double)index.get(word).size());
    }

    /**
     * Comprueba si un documento esta en una lista
     */
    private boolean documentInList(ArrayList<IndexItem> list, int idDoc)
    {
        for(IndexItem item : list) {
            if(item.getDocReference() == idDoc)
                return true;
        }
        return false;
    }

    /*private ArrayList<IndexItem> documentsByWord(String word)
    {
        if(!stopWord.contains(word))
        {
            word = stemmer.getStemmer(word);
            return index.get(word);
        }
        return null;
    }*/

    /**
     * Recibe una cadena y retorna si es o no una stopWord
     */
    public boolean isStopWords(String sText)
    {
        //@TODO Falta completar para eliminar mas tokens
        return sText.equals(".") || sText.equals(":") ||
               sText.equals(",") || sText.equals(";") ||
               sText.equals("/") || sText.equals("=") ||
               sText.equals("(") || sText.equals(")") ||
               sText.equals("?") || sText.equals("¿") ||
               sText.equals("!") || sText.equals("¡") ||
               sText.equals("$") || sText.equals("%") ||
               sText.equals("-") || sText.equals("_") ||
               sText.equals(">") || sText.equals("<") ||
               isNumeric(sText)  ||
               stopWord.containsKey(sText);     
    }

    /**
     * Calcula si un string representa a un numero
     */
    private boolean isNumeric(String cadena){
	try {
		Integer.parseInt(cadena);
		return true;
	} catch (NumberFormatException nfe){
		return false;
	}
    }



    


    @Override
    public String toString()
    {
        String text = "";
        
        Enumeration<String> keys =  index.keys();

        String key;
        while(keys.hasMoreElements())
        {
            key = keys.nextElement();
            text += "WORD("+key+")";
            ArrayList<IndexItem> values = index.get(key);
            for(IndexItem value : values)
                text += "ITEM("+value.toString()+")";
            text += "\n";
        }
        return text;
    }

    private String documentToString()
    {
        String text = "";
        for(Document doc : documents)
            text += doc.toString() + "\n";

        return text;
    }

    public void saveIndex()
    {
        ObjectOutputStream file = null;
        try
        {
            file = new ObjectOutputStream(new FileOutputStream(fileIndexName));
            file.writeObject(this.toString());
            file.close();
        }
        catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            try {
                file.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }


        try
        {
            file = new ObjectOutputStream(new FileOutputStream(fileDocumentsName));
            file.writeObject(documentToString());
            file.close();
        }
        catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            try {
                file.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }

    public void loadIndex()
    {
        ObjectInputStream file = null;
        try
        {
            file = new ObjectInputStream(new FileInputStream(fileIndexName));
            String text = (String)file.readObject();

            //Reconstruimos el fichero a partir de la cadena de entrada
            String[] lines = text.split("WORD");
            for(int i=1; i<lines.length; i++)
            {
                String line = lines[i];                
                int posBraceRight = line.indexOf(")");
                String word = line.substring(1, posBraceRight);                
                line = line.substring(posBraceRight+1);


                ArrayList<IndexItem> list = new ArrayList<IndexItem>();
                String[] items = line.split("ITEM");
                for(int j=1; j<items.length; j++)
                {
                    String item = items[j];
                    item = item.substring(1);
                    int posBraceLeft = item.indexOf("(");
                    int docReference = Integer.parseInt(item.substring(0, posBraceLeft));
                    item = item.substring(posBraceLeft+1);

                    posBraceRight = item.indexOf("))");
                    item = item.substring(0, posBraceRight);

                    ArrayList<Integer> charList = new ArrayList<Integer>();
                    String[] positions = item.split(",");
                    for(int k=0; k<positions.length; k++)
                    {
                        String charPos = positions[k];
                        charList.add(Integer.parseInt(charPos));
                    }

                    IndexItem ii = new IndexItem(docReference);
                    ii.setCharPosition(charList);
                    list.add(ii);
                }

                //Aniadimos al indice
                index.put(word, list);
            }
            file.close();
        }
        catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            try {
                file.close();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }



        try
        {
            file = new ObjectInputStream(new FileInputStream(fileDocumentsName));
            String text = (String)file.readObject();

            //Reconstruimos el fichero a partir de la cadena de entrada
            String[] lines = text.split("DOC");
            for(int i=1; i<lines.length; i++)
            {
                Document doc = new Document();
                String line = lines[i];
                int pos = line.indexOf("##");
                doc.setId(Integer.parseInt(line.substring(1, pos)));
                line = line.substring(pos+1);

                pos = line.indexOf("##");
                doc.setFileName(line.substring(1, pos));
                line = line.substring(pos+1);

                pos = line.indexOf("##");
                doc.setTittle(line.substring(1, pos));
                line = line.substring(pos+1);

                doc.setText(line.substring(1, line.length() - 2));

                //Aniadimos a la lista de documentos
                documents.add(doc);
            }
            file.close();
        }
        catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            try {
                file.close();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

    }
}
